/*
 * Created on 2010-12-09
 * The main class to generate messages.
 */
package algo;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import msg.Msg_o2s_installQuery;
import msg.Msg_o2s_updateLocation;
import msg.Msg_s2o_probe;
import msg.Msg_s2o_queryResult;
import entity.Cell;
import entity.Field;
import entity.MobileRegion;
import entity.MovingObject;
import entity.Statistics;
import entity.Velocity;

/**
 * 
 * 1. implement basic flow [done] <br>
 * 2. test MyEventQ.java [done] <br>
 * 3. test message generator [done] <br>
 * 4. check writeo2s message, duplicate time points[done]<br>
 * 5. check the number of messages (incoming and outgoing) are correct<br>
 * 6. Circle implementation is incorrect. correct it.[done] <br>
 * 7. check eventQ is correct [done]<br>
 * 8. change speed to velocity, which was read from data sets [done] <br>
 * 9. safe region should bound the movement of the moving object. [done] <br>
 * 10 add back the alert region [done]<br>
 * 11.TODO: safe region and alert region collision 12. TODO: no need to register
 * safe region. server can detect its extent then determine the grid
 * 
 * 
 * @author Xiaohui
 * 
 */
public class MGrmd {

	static int cellLength;
	double maxWidth = 0;
	double maxHeight = 0;
	int currentTS;
	boolean end;
	Field field;
	String[] lastORecord;
	static int numObj;
	// int numQueries;
	MovingObject[] objects;
	// HashMap<Integer, MovingObject> q_objects;

	static String ofilePath;
	File o2sMsgFilePath;
	BufferedReader obr;
	BufferedWriter o2sMsgBw;// write out o2s messages.
	BufferedWriter s2oMsgBw;// write out s2o messages.

	Statistics statistics;
	String speedTag;

	// no need
	// List<Integer> enterNewCellMo;

	// no need because every mo has be checked if it exits sr
	// List<Integer> changeLocationMo;

	private static int qRadius;
	private File s2oMsgFilePath;
	private static double mRadius = 40;

	/**
	 * handle Mo exit sr <br>
	 * 1. client sends msg to server <br>
	 * 2. server creates new sr <br>
	 * 
	 * @param currmo
	 */

	private void clientHandleExitMRegion(MovingObject currmo) throws Exception {
		Msg_o2s_updateLocation upd = new Msg_o2s_updateLocation(currentTS,
				currmo.oid);
		// Msg_s2o_probe probemsg = new Msg_s2o_probe(currentTS, currmo.oid);

		// this.putMsgIntoFile(currentTS, probemsg.toString(), false);
		this.putMsgIntoFile(currentTS, upd.toString(), true);
	}

	/**
	 * Find query result for currmo with accurate location
	 * 
	 * @param currmo
	 */
	void findResultFirstTime() {
		MovingObject currmo;
		MovingObject tempmo;
		int counter = 0;
		for (int i = 0; i < objects.length; i++) {
			currmo = objects[i];
			for (int j = i + 1; j < objects.length; j++) {
				tempmo = objects[j];
				if (tempmo == null) {
					System.out.println("null: " + j + ": " + tempmo);
				}

				if (currmo.getDistance(tempmo) < qRadius) {
					currmo.hasFriend = true;
					tempmo.hasFriend = true;
				}
			}
			counter++;
			if (counter % 1000 == 0) {
				System.err.println("Now " + counter);
			}
		}
	}

	/**
	 * Find query result for currmo without accurate location
	 * 
	 * @param currmo
	 */
	void findResult() {
		MovingObject currmo;
		MovingObject tempmo;
		int counter = 0;
		for (int i = 0; i < objects.length; i++) {
			currmo = objects[i];
			for (int j = i + 1; j < objects.length; j++) {
				tempmo = objects[j];
				if (currmo.mRegion.intersects(tempmo.mRegion.getBounds2D())) {
					tempmo.probe = true;
				}
				if (currmo.getDistance(tempmo) < qRadius) {
					currmo.hasFriend = true;
					tempmo.hasFriend = true;
				}
			}
			counter++;
			if (counter % 10000 == 0) {
				System.err.println("Now " + counter);
			}
		}
	}

	public String[] getOneObjectRecord() {
		String line = null;
		try {
			line = this.obr.readLine();
			if (line != null) {
				return line.split("\t");
			} else {
				this.end = true;
				return null;
			}
		} catch (IOException e) {
			System.out.println("in time0process, IOException");
			System.exit(-1);
		}
		return null;
	}

	// public String[] getOneQueryRecord() {
	// String line = null;
	// try {
	// line = this.qbr.readLine();
	// if (line != null) {
	// return line.split("\t");
	// } else {
	// return null;
	// }
	// } catch (IOException e) {
	// System.out.println("in time0process, IOException");
	// System.exit(-1);
	// }
	// return null;
	// }

	// public MovingObject getQ_object(int oid) {
	// return this.q_objects.get(oid);
	// }

	/**
	 * server process <br>
	 * Compute initial results for the queries <br>
	 * go by query; retrieve objects in overlapping cells <br>
	 * send appropriate messages <br>
	 * 
	 * @throws Exception
	 */
	public void initialProcess() throws Exception {
		// real work here: find initial result for every mo
		findResultFirstTime();

		for (MovingObject currmo : this.objects) {
			// o2s to install queries
			Msg_o2s_installQuery miq = new Msg_o2s_installQuery(this.currentTS,
					currmo.oid, currmo.getCenterP());
			// System.out.println(miq.toString());
			this.putMsgIntoFile(this.currentTS, miq.toString(), true);

			// s2o results messages
			if (currmo.hasFriend) {
				Msg_s2o_queryResult miqs2o = new Msg_s2o_queryResult(currentTS,
						currmo.oid);
				this.putMsgIntoFile(this.currentTS, miqs2o.toString(), false);
				currmo.hasFriend = false;
				this.statistics.CanResultMsgCounter++;
			}
		}
	}

	/**
	 * load data whose timepoint is the specified timepoint 1. Update moving
	 * objects' location and velocity. <br>
	 * 2. record which objects should be treated <br>
	 */
	public void loadObjects(int timepoint) {
		// clear the enter new cell first
		// query objects
		MovingObject currmo;
		String[] aORecord = this.lastORecord;
		int readTS = Integer.parseInt(aORecord[4]);
		while (aORecord != null && readTS == timepoint) {
			int qid = Integer.parseInt(aORecord[1]);
			double px = Double.parseDouble(aORecord[5]);
			double py = Double.parseDouble(aORecord[6]);
			currmo = this.objects[qid];

			// move the point to nextP
			// update the alert region
			// update velocity
			currmo.setCenter(currmo.nextP);
			currmo.velocity = new Velocity(px - currmo.getCenterX(), py
					- currmo.getCenterY());

			currmo.nextP = new Point2D.Double(px, py);

			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;
	}

	/**
	 * Create moving objects and moving queries from the data.<br>
	 * read one more round to get velocity <br>
	 * cells don't register objects because of safe region<br>
	 */
	public void loadObjectsFirstTime() {
		MovingObject currmo;
		MobileRegion currmr;
		Point2D.Double tempP;
		Velocity tempV;
		ArrayList<Cell> affectedCells;
		String[] aORecord = this.getOneObjectRecord();
		assert (aORecord != null) : "in time0process, get null record";
		int readTS = Integer.parseInt(aORecord[4]); // time starts from 0
		while (aORecord != null && readTS == 0) {
			int oid = Integer.parseInt(aORecord[1]);
			double px = Double.parseDouble(aORecord[5]);
			double py = Double.parseDouble(aORecord[6]);
			if (px > maxWidth) {
				maxWidth = px;
			}
			if (py > maxHeight) {
				maxHeight = py;
			}

			Point2D p = new Point2D.Double(px, py);
			Cell cell = this.field.inWhichCell(p);
			// double speed = Double.parseDouble(aORecord[7]);

			currmo = new MovingObject(oid, p);
			currmr = new MobileRegion(p, mRadius, oid, null, currentTS);
			currmo.mRegion = currmr;

			objects[oid] = currmo;
			// cell register moving object and query here
			cell.registeredObjects.add(oid);
			affectedCells = field.intersectWith(currmr);
			for (Cell c : affectedCells) {
				c.registeredQueries.add(oid);
			}

			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;

		readTS = Integer.parseInt(aORecord[4]);
		while (aORecord != null && readTS == 1) {
			int qid = Integer.parseInt(aORecord[1]);
			double px = Double.parseDouble(aORecord[5]);
			double py = Double.parseDouble(aORecord[6]);

			currmo = this.objects[qid];
			tempP = new Point2D.Double(px, py);

			// Mobile region is created here.
			// Three fields are created when seeing locations one more next time
			// point
			currmo.nextP = tempP;
			tempV = new Velocity(px - currmo.getCenterX(), py
					- currmo.getCenterY());
			currmo.velocity = tempV;
			currmo.mRegion.v = tempV;

			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;
	}

	public MGrmd(int numCellsPerRow, int numCellsPerCol, String speedtag,
			String o2sMsgFileName, String s2oMsgFileName, String statsFileName)
			throws Exception {

		this.field = new Field(numCellsPerRow, numCellsPerCol, cellLength);
		this.objects = new MovingObject[numObj];

		// this.queryTable = new HashMap<Integer, QueryTableEntry>();
		this.speedTag = speedtag;

		this.statistics = new Statistics();
		this.end = false;

		// file handle
		try {
			FileReader ofr = new FileReader(ofilePath);
			this.obr = new BufferedReader(ofr);
		} catch (FileNotFoundException e) {
			System.out
					.println("In MessageGenerator(), file not found exception: "
							+ ofilePath);
			System.exit(-1);
		}

		this.start(o2sMsgFileName, s2oMsgFileName, statsFileName);
	}

	public static void main(String[] args) throws Exception {

		// String q_Oldenburg_5k_slow = "D:/Research/data_gen/dismknn/q10k.txt";

		// parameters for oldenburg data
		// String dataFilepath = "E:/Research/MCQ/LSMCQ/expt/olden_10k.dat";

		// cellLength=20, (1200, 1600)
		// cellLength=40 (600, 800)
		// cellLength=60, (400, 600)
		// celllength=80 (300, 400)
		// cellLegth = 100, (250, 350)
		cellLength = 40;
		int olden_numCellsRow = 600;
		int olden_numCellsCol = 800;

		// int qRadius = 30;
		// int mRadius = 40;
		// parse args here

		if (args.length != 4) {
			System.out.println("usage: o_dataset numObj qRadius mRadius");
			System.exit(0);
		} else {
			ofilePath = args[0];
			numObj = Integer.parseInt(args[1]);
			qRadius = Integer.parseInt(args[2]);
			mRadius = Integer.parseInt(args[3]);
			//
			// o_dataset = args[0];
			// oldenCellLength = Integer.parseInt(args[1]);
			// speedTag = args[2];
		}

		String prog = "RMD";
		String sub = ofilePath.substring(0, ofilePath.indexOf("."));
		String olden_o2sMsgFile = prog + sub + "_" + qRadius + "_" + cellLength
				+ "_" + mRadius + "_O2Smsgs.txt";
		String olden_s2oMsgFile = prog + sub + "_" + qRadius + "_" + cellLength
				+ "_" + mRadius + "_S2Omsgs.txt";
		String olden_statsFile = prog + sub + "_" + qRadius + "_" + cellLength
				+ "_" + mRadius + "_stats.txt";
		// System.out.println("file names done");

		// test parameters (10x10 grids)
		String testData = "E:/Research/MCQ/LSMCQ/expt/test3mo.txt";
		String testMsgFile = "E:/Research/MCQ/LSMCQ/expt/test_msgs.txt";
		int test_CellLength = 1;
		int test_numCellsRow = 10;
		int test_numCellsCol = 10;
		int test_radius = 3;
		int test_numObj = 3;

		String speedTag = "slow";

		// System.exit(-1);
		// MessageGenerator mg = new MessageGenerator(olden_numCellsRow,
		// olden_numCellsCol, oldenCellLength, o_dataset, olden_radius,
		// speedTag);

		// MGSimple mg1 = new MGSimple(test_numObj, test_numCellsRow,
		// test_numCellsCol, test_CellLength, testData, test_radius,
		// speedTag, testMsgFile);
		//
		System.out.println("*****MGRMD*******");
		// System.out.println(dataFilepath);
		long t1 = System.currentTimeMillis();
		MGrmd mg_real = new MGrmd(olden_numCellsRow, olden_numCellsCol,
				speedTag, olden_o2sMsgFile, olden_s2oMsgFile, olden_statsFile);
		long t2 = System.currentTimeMillis();
		System.out.println("Time Elapsed(s):" + (t2 - t1) / 1000.0);
	}

	// void prepareMsgFile() {
	// // prepare msg output file
	// String msgFileName = "o2sMsgs_o" + this.numObjects + "_c"
	// + this.cellLength + "_" + this.speedTag + ".txt";
	// this.o2sMsgFilePath = new File(msgFileName);
	// this.s2oMsgFilePath = new File(s2oMsgFileName);
	// try {
	// FileWriter o2sFw = new FileWriter(this.o2sMsgFilePath);
	//
	// this.o2sMsgBw = new BufferedWriter(o2sFw);
	// this.o2sMsgBw = new BufferedWriter(s2oFw);
	//
	// } catch (IOException e) {
	// System.out.println("In MessageGenerator(), IO exception");
	// System.exit(-1);
	// }
	// }

	void prepareMsgFile(String o2sMsgFileName, String s2oMsgFileName) {
		// prepare msg output file

		this.o2sMsgFilePath = new File(o2sMsgFileName);
		this.s2oMsgFilePath = new File(s2oMsgFileName);
		try {
			FileWriter o2sFw = new FileWriter(this.o2sMsgFilePath);
			FileWriter s2oFw = new FileWriter(this.s2oMsgFilePath);
			this.o2sMsgBw = new BufferedWriter(o2sFw);
			this.s2oMsgBw = new BufferedWriter(s2oFw);
		} catch (IOException e) {
			System.out.println("In MessageGenerator(), IO exception");
			System.exit(-1);
		}
	}

	/**
	 * public void writeOutCells() { String cellFileName = "cellConfig_" +
	 * this.cellR + ".txt"; this.cellOutPath = new File(cellFileName); try {
	 * FileWriter cellFw = new FileWriter(this.cellOutPath); this.cbw = new
	 * BufferedWriter(cellFw); } catch (IOException e) {
	 * System.out.println("In MessageGenerator(), IO exception");
	 * System.exit(-1); }
	 * 
	 * for (int r = 1; r <= this.field.CMatrix.numRows; r++) { CellRow cr =
	 * this.field.CMatrix.getRow(r); for (int c = 1; c < cr.cellColumns.size();
	 * c++) { Cell cell = (Cell) cr.cellColumns.get(c); String ps = cell.cid.row
	 * + " " + cell.cid.column + " " + cell.center.x + " " + cell.center.y + " "
	 * + cell.sideLength; try { this.cbw.write(ps + "\n"); } catch (IOException
	 * e) { System.out.println("In printAllObjects(), IO exception");
	 * System.exit(-1); } } }
	 * 
	 * try { this.cbw.flush(); } catch (IOException e) {
	 * System.out.println("In convert(), IO exception"); System.exit(-1); }
	 * 
	 * }
	 **/
	/**
	 * @return a String array representing a record; null when come to the end
	 *         of the file. read a record from the input file.
	 */

	void parseLine(String line) {
		String[] subStrings = line.split("\t");
		// System.out.println(subStrings.length);
		for (int i = 0; i < subStrings.length; i++) {
			System.out.println(subStrings[i]);
		}
		if (subStrings.length == 10) {
			String recordTyppe = subStrings[0];
			System.out.println(recordTyppe);

			if (recordTyppe.equals("newpoint")) {
				System.out.println("a new point is generated, with oid "
						+ subStrings[1]);

			} else if (recordTyppe.equals("point")) {
				System.out.println("point " + subStrings[1] + " moves");

			} else if (recordTyppe.equals("disappearpoint")) {
				System.out.println("point " + subStrings[1] + " disappears");
			}
		}
	}

	/**
	 * Essential method to handle object movement for each query<br>
	 * update query result <br>
	 * 
	 * @throws Exception
	 * 
	 */
	public void processCurrentTime() throws Exception {
		// mq moves
		// 1. client checks if itself exits its safe region
		// 2. server checks if current safe region collides with alert region of
		// some other mo.
		for (MovingObject currmo : this.objects) {
			// handle safe region to generate messages
			if (!currmo.mRegion.contains(currmo.getCenterP())) {
				// a mo exits its safe region
				long ct1 = System.currentTimeMillis();
				this.clientHandleExitMRegion(currmo);
				long ct2 = System.currentTimeMillis();
				this.statistics.clientWorklod += (ct2 - ct1);

				long t1 = System.currentTimeMillis();
				this.serverUpdateMRegion(currmo);
				long t2 = System.currentTimeMillis();
				this.statistics.serverWorkLoad += (t2 - t1);
			} else {
				long t1 = System.currentTimeMillis();
				this.serverHandleMRegionMove(currmo);
				long t2 = System.currentTimeMillis();
				this.statistics.serverWorkLoad += (t2 - t1);
			}
		}
	}

	/**
	 * 
	 * @param msgTime
	 * @param msgString
	 * @param incoming
	 *            : true if o2s, else false
	 */
	void putMsgIntoFile(int msgTime, String msgString, boolean incoming)
			throws Exception {
		// int key = msgTime;
		// System.out.println("msg time; " + msgTime);
		// System.out.println(this.timeMsgMap_o2s.keySet());
		// if (this.timeMsgMap_o2s.containsKey(msgTime)) {
		// msgList = this.timeMsgMap_o2s.get(key);
		// msgList.add(msgString);
		// } else {
		// msgList = new ArrayList<String>();
		// msgList.add(msgString);
		// }
		if (incoming) {
			this.o2sMsgBw.write(msgString);
			this.o2sMsgBw.newLine();
			this.statistics.updateMsgCounter++;
		} else {
			// two types of messages: probe or canResult
			this.s2oMsgBw.write(msgString);
			this.o2sMsgBw.newLine();
		}
	}

	//
	// public void processNTS() {
	// for (int qid : q_objects.keySet()) {
	// // System.out.println(this.queryTable.get(qid));
	// double pre_event_time = 0;
	// Msg_o2s_criticalObjectChange event = this.aQuerysNextEvent(qid,
	// pre_event_time);
	// while (event != null) {
	// // System.out.println(event);
	// this.putMsgIntoMap(event.timepoint, event.toString());
	//
	// event = this.aQuerysNextEvent(qid, event.timepoint
	// - this.currentTS);
	// }
	// }
	// }

	/**
	 * @param qid
	 * @param pre_event_time
	 * @return -1 if no critical object event will happen between pre_event_time
	 *         and 1; otherwise the time point of this event. Note that the
	 *         event is processed in this method.
	 */
	// public Msg_o2s_criticalObjectChange aQuerysNextEvent(int qid,
	// double pre_event_time) {
	// Msg_o2s_criticalObjectChange mcoc = null;
	// QueryTableEntry qte = (QueryTableEntry) this.queryTable
	// .get(new Integer(qid));
	// int co_id = qte.criticalOid;
	// double earlist = 1.00001;
	// int causingOid = 0;// todo: is it ok to init it as 0?
	// for (CellID aCid : qte.affectedCells) {
	// Cell aCell = this.getCell(aCid);
	// for (int oid : aCell.registeredObjects) {
	// if (oid != co_id) {
	// double eventTime = this.estimateCriticalChangeTime(qid,
	// co_id, oid, pre_event_time);
	// if (eventTime != -1 && eventTime > pre_event_time + 0.00001) {
	// if (eventTime < earlist) {
	// earlist = eventTime;
	// causingOid = oid;
	// } else if (eventTime == earlist) {
	// System.err.println("simultaneous events detected");
	// }
	// }
	// }
	// }
	// }
	// if there is an event, process
	// if (earlist < 1.00001) {
	// MovingObject causingMO = this.getObject(causingOid);
	// // calculate the position of the causingMO at this time
	// Location eventLoc = causingMO.locationAfterTime(earlist);
	// // TODOne: o2s critical change message
	// mcoc = new Msg_o2s_criticalObjectChange(this.currentTS + earlist,
	// qid, causingOid, eventLoc, causingMO.v, co_id);
	// }
	// return mcoc;
	// }

	// /**
	// * update objects' cells.
	// */
	// public void postProcessNTS_O() {
	// // todo: what is done here?
	// // todo: bug: cell is updated only at the end of a time unit
	// for (Integer i : objects.keySet()) {
	// MovingObject mo = (MovingObject) this.objects.get(i);
	// if (mo.nextP != null) {
	// CellID cid = this.field.inWhichCell(mo.nextP);
	// if (!mo.registeredCell.equals(cid)) {
	// // un-register the mo from previsous cell
	// CellID pre_cid = mo.registeredCell;
	// Cell pre_cell = this.getCell(pre_cid);
	// pre_cell.unregister(mo.oid);
	//
	// // test whether fly happens
	// assert (pre_cell.neighboringCells.contains(cid)) :
	// "in postProcessNTS_O, the object flies into a non-neighboring cell";
	// // register it to the new cell
	// Cell cur_cell = this.getCell(cid);
	// cur_cell.register(mo.oid);
	// // delta queries, minus, plus
	// mo.registeredCell = cid;
	// // TODOne: o2s cell change message
	// int fromObject = 0;
	// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(
	// this.currentTS + 1, fromObject, mo.oid, pre_cid,
	// cid);
	// this.putMsgIntoMap((this.currentTS + 1), mec.toString());
	// this.handleEnterCell(mo.oid, pre_cid, cid);
	// }
	// mo.currP = mo.nextP;
	// }
	// }
	// }

	// /**
	// * update queries' cells.
	// */
	// public void postProcessNTS_Q() {
	// for (QueryTableEntry qte : this.queryTable.values()) {
	// int qoid = qte.focalOid;
	// MovingObject qmo = this.getQ_object(qoid);
	// if (qmo.nextP != null) {
	// CellID cid = this.field.inWhichCell(qmo.nextP);
	// if (!qmo.registeredCell.equals(cid)) {
	// CellID pre_cid = qmo.registeredCell;
	// int fromQuery = 1;
	// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(
	// this.currentTS + 1, fromQuery, qmo.oid, pre_cid,
	// cid);
	// this.putMsgIntoMap((this.currentTS + 1), mec.toString());
	// qmo.registeredCell = cid;
	// }
	// qmo.currP = qmo.nextP;
	// }
	// }
	// }

	// public void NTS() {
	// this.processNTS();
	// this.postProcessNTS_O();
	// this.postProcessNTS_Q();
	// this.currentTS++;
	// this.writeOutO2sMsgs();
	// this.sampleObjectsWorkload();
	// }

	// public void continuousNTS() {
	// while (!this.end) {
	// this.NTS();
	// System.err.println("ts:" + this.currentTS);
	// }
	// this.stat.calculateStatistics(this.objects.size(),
	// this.q_objects.size(), this.currentTS);
	// this.writeOutStatistics();
	// this.printStatisticsToScreen();
	// }

	public void printStatisticsToScreen(String str) {
		System.out.println(str);
	}

	// public void checkACellForNearerObjects(CellID cid, int qid,
	// Location focolLoc, boolean withCD, double distance,
	// DisObjsMap disObjMap) {
	// Cell cell = this.field.CMatrix.getCell(cid);
	// for (int oid : cell.registeredObjects) {
	// MovingObject mo = this.getObject(oid);
	// if (withCD) {
	// double nearerDis = mo.nearer(focolLoc, distance);
	// if (nearerDis > 0) {
	// disObjMap.put(nearerDis, oid);
	// // TODOne:o2s reply message
	// Msg_o2s_reply mr = new Msg_o2s_reply(this.currentTS, qid,
	// mo.oid, mo.currP, mo.v, mo.registeredCell);
	// this.putMsgIntoMap(this.currentTS, mr.toString());
	// }
	// } else {
	// double dis = mo.currP.distance(focolLoc);
	// if (dis == 0) {
	// System.err
	// .println("the object is at the same location as the query object");
	// }
	// if (disObjMap.containsKey(new Double(dis))) {
	// System.err
	// .println("objects with a same distance to the query object are found");
	// }
	// disObjMap.put(dis, oid);
	// // TODOne:o2s reply message
	// Msg_o2s_reply mr = new Msg_o2s_reply(this.currentTS, qid,
	// mo.oid, mo.currP, mo.v, mo.registeredCell);
	// this.putMsgIntoMap(this.currentTS, mr.toString());
	// }
	// }
	//
	// }

	// public void printObjectsOwnQuery() {
	// for (int i = 0; i < this.numObjects; i++) {
	// MovingObject mo = (MovingObject) this.objects.get(new Integer(i));
	// mo.printOwnQueries();
	// }
	// }

	// public void printObjects() {
	// System.out.println("======================");
	// System.out.println("==========Moving Objects==========, at time "
	// + this.currentTS);
	// for (int i = 0; i < this.objects.size(); i++) {
	// MovingObject mo = this.getObject(i);
	// System.out.println(mo);
	// }
	// }
	//
	// public void printQueryObjects() {
	// System.out.println("======================");
	// System.out.println("==========Query Objects==========, at time "
	// + this.currentTS);
	// for (int i = 0; i < this.objects.size(); i++) {
	// MovingObject mo = this.objects.get(i);
	// System.out.println(mo);
	// }
	// }

	// /**
	// * write out the o2s messages. This also makes the message queue empty.
	// */
	// public void writeOutO2sMsgs() {
	// System.out.println("num msg:" + timeMsgMap_o2s.size());
	//
	// for (String msg : timeMsgMap_o2s) {
	// try {
	// this.o2sMsgBw.write(msg + "\n");
	// } catch (IOException e) {
	// System.out.println("In writeOutO2sMsgs(), IO exception");
	// System.exit(-1);
	// }
	// }
	//
	// this.timeMsgMap_o2s.clear();
	//
	// try {
	// this.o2sMsgBw.flush();
	// } catch (IOException e) {
	// System.out.println("In writeOutO2sMsgs(), IO exception");
	// System.exit(-1);
	// }
	// }

	public void writeOutStatistics(String statsFileName, String str) {

		// write statistics out
		try {
			FileWriter stat_fw = new FileWriter(statsFileName);
			BufferedWriter sbw = new BufferedWriter(stat_fw);
			sbw.write(str);
			sbw.newLine();

			sbw.close();
		} catch (IOException e) {
			System.out.println("In MessageGenerator(), IO exception");
			System.exit(-1);
		}

	}

	/**
	 * This method does not contain computation of query results. <br>
	 * server handles sr move <br>
	 * only need to check colllision between a safe region and an alert region <br>
	 * 
	 * @param newCells
	 * @param oldCells
	 * 
	 * @param currmo
	 * @throws Exception
	 */
	private void serverHandleMRegionMove(MovingObject currmo) throws Exception {
		ArrayList<Cell> oldCells, newCells;
		Point2D tempP;
		int ti = currentTS - currmo.mRegion.creationTime;
		// calculate where the safe region should be
		tempP = currmo.mRegion.getCenterP();
		double x1 = tempP.getX() + currmo.mRegion.v.getVx() * ti;
		double y1 = tempP.getY() + currmo.mRegion.v.getVy() * ti;
		int[] extent = currmo.mRegion.convertExtent2Grid(field.cellLength);
		oldCells = field.intersectWith(currmo.mRegion);

		currmo.mRegion.setCenter(x1, y1);
		currmo.mRegion.updateExtent();
		int[] newExtent = currmo.mRegion.convertExtent2Grid(field.cellLength);
		if (extent[0] != newExtent[0] || extent[1] != newExtent[1]
				|| extent[2] != newExtent[2] || extent[3] != newExtent[3]) {
			newCells = field.intersectWith(currmo.mRegion);
			for (Cell cell : newCells) {
				if (!oldCells.contains(cell)) {
					currmo.hasFriend = true;
					for (Integer i : cell.registeredQueries) {
						objects[i].hasFriend = true;
						objects[i].probe = true;
					}
				}
			}

			// s2o: send probe messages
			// s2o: send candidate result messages
			for (MovingObject mo : objects) {
				if (mo.probe) {
					Msg_s2o_probe probeMsg = new Msg_s2o_probe(currentTS,
							currmo.oid);
					this.putMsgIntoFile(currentTS, probeMsg.toString(), false);
					mo.probe = false;
					this.statistics.probeMsgCounter++;
				}
				if (mo.hasFriend) {
					Msg_s2o_queryResult canResult = new Msg_s2o_queryResult(
							currentTS, currmo.oid);
					this.putMsgIntoFile(currentTS, canResult.toString(), false);
					mo.hasFriend = false;
					this.statistics.CanResultMsgCounter++;
				}
			}

		}
	}

	private void serverUpdateMRegion(MovingObject currmo) {
		// server update the mobile region
		currmo.mRegion.setCenter(currmo.getCenterP());
		currmo.mRegion.v = currmo.velocity;
		currmo.mRegion.creationTime = this.currentTS;
	}

	/**
	 * Initial processing
	 * 
	 * @throws Exception
	 */
	public void start(String o2sMsgFileName, String s2oMsgFileName,
			String statsFileName) throws Exception {
		this.currentTS = 0;
		this.prepareMsgFile(o2sMsgFileName, s2oMsgFileName);
		System.out.println("Load data... ");
		this.loadObjectsFirstTime();

		System.out.println("Initial Process...");
		this.initialProcess();

		// for (int k : objects.keySet()) {
		// MovingObject mo = objects.get(k);
		// System.out.println("mo" + k + ": " + mo.currResult + "; "
		// + mo.satisfiedQueries);
		// }

		// System.out.println("query and cell relation: ");

		// continuous processing
		while (!this.end) {
			this.currentTS++;
			System.out.println("ts: " + this.currentTS);
			// read t_(i+1) data at time t_i and
			// check whose velocity change
			this.loadObjects(currentTS + 1);
			// update result
			this.processCurrentTime();

			// this.sampleObjectsWorkload();

			// System.out.println("ts:" + this.currentTS);
			// for (int k : objects.keySet()) {
			// MovingObject mo = objects.get(k);
			//
			// System.out.println(k + ": " + mo.currResult + "; "
			// + mo.satisfiedQueries);
			// }
		}

		String outString = "******MGRMD*******\n" + "o dataset: " + ofilePath
				+ "\n" + "Field:" + maxWidth + "x" + maxHeight + "\n"
				+ "# objects:" + numObj + "\n" + "# timestamps: "
				+ this.currentTS + "\n" + "Query radius: " + qRadius + "\n"
				+ "Cell Length: " + cellLength + "\n" + "speed tag: "
				+ this.speedTag + "\n" + "mobile region radius: " + mRadius
				+ "\n" + "#o2s updateMsg: " + this.statistics.updateMsgCounter
				+ "\n" + "#s2o probeMsg: " + +this.statistics.probeMsgCounter
				+ "\n" + "#s2o canResultMsg: "
				+ +this.statistics.CanResultMsgCounter + "\n"
				+ "server workload(s): "
				+ this.statistics.getServerWorkLoadSec() + "\n"
				+ "client msg rate: "
				+ statistics.getUpdateMsgRate(currentTS, numObj) + "\n";
		// + stat.getServerMsgRate(this.currentTS) + "\n"
		// + "client msg rate: "
		// + stat.getClientMsgRate(currentTS, numObjects);
		this.writeOutStatistics(statsFileName, outString);
		this.printStatisticsToScreen(outString);
	}
}
